Skip to content

parse the timer interrupt only once#2555

Open
stlankes wants to merge 3 commits into
hermit-os:mainfrom
stlankes:timer
Open

parse the timer interrupt only once#2555
stlankes wants to merge 3 commits into
hermit-os:mainfrom
stlankes:timer

Conversation

@stlankes

@stlankes stlankes commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

init() (boot core) and init_cpu() (application processors) both parsed the "arm,armv8-timer" node of the device tree to find the timer interrupt, and each picked its own triplet out of the four the node lists. The timer is a PPI, so every core enables it in its own redistributor — if the two parsers ever disagree about which interrupt the kernel uses, the application processors enable an interrupt that never fires and tasks sleeping on them are not woken up again.

Parse the node once in init(), cache the interrupt and its trigger mode, and let both callers enable it from there. No functional change: both sites selected the Non-secure Phys timer before.

…_) updated the FDT parsing in init(), which now selects the third interrupt triplet of the "arm,armv8-timer" node (Virtual, PPI11 / INTID 27). init_cpu() still selected the second triplet (Non-secure Phys, PPI 14 / INTID 30), so every application processor enabled the physical timer PPI in its redistributor while set_oneshot_timer programs the virtual timer. Timer PPIs are per-core: sleeping tasks on the secondary cores were never woken up again.

This is why thread_test with --smp 4 timed out: of eight threads only the two scheduled on the boot core finished; the six on the secondary cores slept forever.

Skip the same two triplets in init_cpu() so all cores enable the virtual timer interrupt.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark Results

Details
Benchmark Current: 2f10654 Previous: 2e23902 Performance Ratio
startup_benchmark Build Time 83.58 s 80.34 s 1.04
startup_benchmark File Size 0.80 MB 0.80 MB 1.00
Startup Time - 1 core 0.74 s (±0.02 s) 0.75 s (±0.02 s) 0.99
Startup Time - 2 cores 0.76 s (±0.03 s) 0.74 s (±0.02 s) 1.03
Startup Time - 4 cores 0.77 s (±0.02 s) 0.74 s (±0.02 s) 1.04
multithreaded_benchmark Build Time 83.31 s 82.11 s 1.01
multithreaded_benchmark File Size 0.86 MB 0.86 MB 1.00
Multithreaded Pi Efficiency - 2 Threads 87.58 % (±6.22 %) 85.89 % (±6.61 %) 1.02
Multithreaded Pi Efficiency - 4 Threads 43.74 % (±3.34 %) 43.43 % (±2.56 %) 1.01
Multithreaded Pi Efficiency - 8 Threads 25.77 % (±1.77 %) 25.76 % (±1.53 %) 1.00
micro_benchmarks Build Time 81.82 s 80.40 s 1.02
micro_benchmarks File Size 0.86 MB 0.86 MB 1.00
Scheduling time - 1 thread 62.81 ticks (±2.67 ticks) 62.65 ticks (±4.06 ticks) 1.00
Scheduling time - 2 threads 34.54 ticks (±3.27 ticks) 34.08 ticks (±4.10 ticks) 1.01
Micro - Time for syscall (getpid) 3.41 ticks (±0.63 ticks) 3.45 ticks (±0.58 ticks) 0.99
Memcpy speed - (built_in) block size 4096 80881.38 MByte/s (±55975.71 MByte/s) 82448.38 MByte/s (±56997.13 MByte/s) 0.98
Memcpy speed - (built_in) block size 1048576 30579.67 MByte/s (±24668.67 MByte/s) 30585.98 MByte/s (±24707.84 MByte/s) 1.00
Memcpy speed - (built_in) block size 16777216 27275.90 MByte/s (±22562.89 MByte/s) 26340.06 MByte/s (±21720.96 MByte/s) 1.04
Memset speed - (built_in) block size 4096 81242.92 MByte/s (±56187.03 MByte/s) 82292.76 MByte/s (±56891.50 MByte/s) 0.99
Memset speed - (built_in) block size 1048576 31301.21 MByte/s (±25085.83 MByte/s) 31323.85 MByte/s (±25145.86 MByte/s) 1.00
Memset speed - (built_in) block size 16777216 28037.25 MByte/s (±23027.25 MByte/s) 27104.68 MByte/s (±22209.94 MByte/s) 1.03
Memcpy speed - (rust) block size 4096 76357.44 MByte/s (±53096.30 MByte/s) 74097.96 MByte/s (±51811.44 MByte/s) 1.03
Memcpy speed - (rust) block size 1048576 30211.89 MByte/s (±24552.80 MByte/s) 30361.60 MByte/s (±24602.37 MByte/s) 1.00
Memcpy speed - (rust) block size 16777216 27155.42 MByte/s (±22362.35 MByte/s) 27625.34 MByte/s (±22806.88 MByte/s) 0.98
Memset speed - (rust) block size 4096 76991.47 MByte/s (±53514.52 MByte/s) 74373.47 MByte/s (±51976.48 MByte/s) 1.04
Memset speed - (rust) block size 1048576 30955.12 MByte/s (±24975.91 MByte/s) 31110.89 MByte/s (±25033.24 MByte/s) 0.99
Memset speed - (rust) block size 16777216 27917.48 MByte/s (±22830.70 MByte/s) 28386.93 MByte/s (±23265.03 MByte/s) 0.98
alloc_benchmarks Build Time 77.31 s 74.76 s 1.03
alloc_benchmarks File Size 0.87 MB 0.87 MB 1.00
Allocations - Allocation success 91.31 % 91.31 % 1
Allocations - Deallocation success 100.00 % 100.00 % 1
Allocations - Pre-fail Allocations 61.44 % 61.44 % 1
Allocations - Average Allocation time 6541.65 Ticks (±423.78 Ticks) 5860.58 Ticks (±98.43 Ticks) 1.12
Allocations - Average Allocation time (no fail) 7318.13 Ticks (±352.43 Ticks) 6554.81 Ticks (±92.86 Ticks) 1.12
Allocations - Average Deallocation time 2460.50 Ticks (±542.94 Ticks) 1805.01 Ticks (±250.35 Ticks) 1.36
mutex_benchmark Build Time 76.80 s 79.82 s 0.96
mutex_benchmark File Size 0.86 MB 0.86 MB 1.00
Mutex Stress Test Average Time per Iteration - 1 Threads 12.04 ns (±0.28 ns) 12.10 ns (±0.41 ns) 1.00
Mutex Stress Test Average Time per Iteration - 2 Threads 40.56 ns (±2.20 ns) 40.26 ns (±1.68 ns) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@stlankes stlankes changed the title enable the virtual timer interrupt on application processors parse the timer interrupt only once Jul 15, 2026
test_stdin fed each message to the guest with a one-second pause and
then terminated QEMU immediately, reading the echoed output only
afterwards. On a slow target the last echo had not been written yet
when QEMU was killed, so the collected output ended mid-line and the
assertion failed intermittently. This showed up on aarch64_be, which
is emulated without hardware acceleration.

Collect the guest's stdout on a background thread and wait until every
message has been echoed (bounded by a 60 s timeout) before terminating
QEMU. The check no longer depends on the guest keeping pace with a
fixed schedule.
@stlankes

Copy link
Copy Markdown
Contributor Author

PR includes #2558 to pass the CI tests.

init() and init_cpu() each parsed the "arm,armv8-timer" device-tree node
on their own and picked a triplet by hand, so the two could disagree about
which interrupt the kernel uses. The kernel arms the physical timer
(CNTP_CVAL_EL0/CNTP_CTL_EL0), whose PPI is the Non-secure Phys triplet
(INTID 30), but init_cpu() selected the Virtual triplet (INTID 27). Timer
PPIs are per-core, so every application processor enabled an interrupt that
never fires and tasks sleeping on the secondary cores were never woken up
again — thread_test hung until the QEMU timeout on aarch64_be with SMP 4.

Parse the timer interrupt once in init(), cache the resolved IntId and
trigger mode in TIMER_IRQ, and let init_cpu() enable that exact interrupt
in its redistributor. This removes the duplicated FDT parsing that let the
two sites drift apart.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants